Skip to content

feat: implement Longest() for stdlib compatibility (Fixes #26) - #27

Merged
kolkov merged 2 commits into
mainfrom
feat/longest-support
Dec 8, 2025
Merged

kolkov merged 2 commits into
mainfrom
feat/longest-support

Conversation

@kolkov

@kolkov kolkov commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

Summary

Implements proper Longest() method that switches from leftmost-first (Perl) to leftmost-longest (POSIX) matching semantics.

Before: Longest() was a no-op stub with misleading documentation claiming coregex used leftmost-longest by default.

After: Longest() actually works, matching Go stdlib regexp.Regexp.Longest() behavior.

Changes

  • Add longest bool flag to Regex struct
  • Implement SetLongest() in meta.Engine and nfa.PikeVM
  • Modify PikeVM search functions to support longest semantics:
    • searchUnanchoredAt()
    • searchUnanchoredWithCapturesAt()
    • searchAt()
    • searchAtWithCaptures()
  • Fix misleading documentation
  • Add comprehensive tests

Behavior

Pattern Input Default (leftmost-first) After Longest() (leftmost-longest)
(a|ab) "ab" "a" "ab"
(#|#!) "#!a" "#" "#!"
(cat|catalog) "catalog" "cat" "catalog"

Performance

No regression in default mode!

Package Change
Lazy DFA -9.37% (faster)
OnePass -15.87% (faster)
Meta +5.58% (compile time only)
Prefilter -4.48% (faster)

Test plan

  • go test ./... passes
  • TestLongest - verifies behavior change after Longest()
  • TestLongestMatchesStdlib - verifies match with Go stdlib
  • Benchmark comparison shows no regression

Fixes #26

…#26)

- Add longest bool flag to Regex struct
- Implement SetLongest() in meta.Engine and PikeVM
- Modify searchUnanchoredAt and searchUnanchoredWithCapturesAt for longest support
- Modify searchAt and searchAtWithCaptures for longest support
- Fix misleading documentation (was claiming leftmost-longest by default)
- Add TestLongest and TestLongestMatchesStdlib tests
- No performance regression in default (leftmost-first) mode

Default: leftmost-first (Perl semantics) - first alternative wins
Longest(): leftmost-longest (POSIX semantics) - longest match wins

Example:
  re := coregex.MustCompile(`(a|ab)`)
  re.FindString("ab")    // "a" (default: first branch wins)
  re.Longest()
  re.FindString("ab")    // "ab" (longest match wins)
@github-actions

github-actions Bot commented Dec 8, 2025

Copy link
Copy Markdown

Benchmark Comparison

Comparing main → PR #27

Summary: geomean 404.8n 391.1n -3.38%

⚠️ Potential regressions detected:

Accelerate/memchr3-4       338.8n ± ∞ ¹   340.9n ± ∞ ¹   +0.62% (p=0.024 n=5)
LazyDFASimpleLiteral-4     697.7n ± ∞ ¹   701.4n ± ∞ ¹   +0.53% (p=0.008 n=5)
geomean                               ³                +0.00%               ³
geomean                               ³                +0.00%               ³
geomean                         ³                +0.00%               ³
geomean                         ³                +0.00%               ³
Compile/\d+-4                                           1.793µ ± ∞ ¹    1.907µ ± ∞ ¹   +6.36% (p=0.008 n=5)
Find/hello-4                                            705.5n ± ∞ ¹    722.9n ± ∞ ¹   +2.47% (p=0.008 n=5)
Find/\d+-4                                              661.0n ± ∞ ¹    667.9n ± ∞ ¹   +1.04% (p=0.008 n=5)
ReverseSuffix_IsMatch_Comparison/coregex_txt_32KB-4     582.5n ± ∞ ¹    658.9n ± ∞ ¹  +13.12% (p=0.008 n=5)

Full results available in workflow artifacts. CI runners have ~10-20% variance.
For accurate benchmarks, run locally: ./scripts/bench.sh --compare

The linter was complaining about gocognit (>40) and nestif (>6) in the
longest match logic. Extracted match comparison into isBetterMatch()
helper method and simplified the remaining match detection blocks.
@kolkov
kolkov merged commit bf43ea5 into main Dec 8, 2025
7 checks passed
@kolkov
kolkov deleted the feat/longest-support branch December 8, 2025 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Longest() method for stdlib compatibility

1 participant